﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Text;

namespace Hu01
{
	public class Hu01Stream : Stream
	{
		BinaryReader reader;
		bool readHeader;
		byte[] outputBuffer;
		int outputPosition;
		uint size;

		public Hu01Stream(Stream baseStream)
		{
			this.reader = new BinaryReader(baseStream);
			this.readHeader = false;
		}

		protected override void Dispose(bool disposing)
		{
			if (disposing)
				this.reader.Close();
			base.Dispose(disposing);
		}

		public override bool CanRead
		{
			get { return true; }
		}

		public override bool CanSeek
		{
			get { return false; }
		}

		public override bool CanWrite
		{
			get { return false; }
		}

		public override void Flush()
		{
		}

		public override long Length
		{
			get { throw new NotSupportedException(); }
		}

		public override long Position
		{
			get
			{
				throw new NotSupportedException();
			}
			set
			{
				throw new NotSupportedException();
			}
		}

		private static void DecodeBlock(HuffmanTree tree, InputBuffer inputBuffer, byte[] outputBuffer)
		{
			int outputOffset = 0;
			while (outputOffset < outputBuffer.Length)
			{
				short code = tree.Lookup(inputBuffer);
				if (code < 0x100)
				{
					// Regular character
					outputBuffer[outputOffset++] = (byte)code;
				}
				else
				{
					// Read offset
					byte offsetBitLength = (byte)((code >> 4) & 0xf);
					uint offset = (uint)(1 << offsetBitLength) | inputBuffer.PeekBits(offsetBitLength);
					int sourceOffset = outputOffset - (int)offset;
					// Read number of bytes to copy
					int bytesToCopy = code & 0xf;
					if (bytesToCopy == 0xf)
					{
						byte nextByte = inputBuffer.ReadByte();
						if (nextByte < 0xff)
							bytesToCopy += nextByte;
						else
							bytesToCopy = inputBuffer.ReadUInt16();
					}
					bytesToCopy += 3;
					// Beware overlapping arrays!
					while (bytesToCopy > 0)
					{
						outputBuffer[outputOffset++] = outputBuffer[sourceOffset++];
						bytesToCopy--;
					}
					inputBuffer.SkipBits(offsetBitLength);
				}
			}
		}
		
		public override int Read(byte[] buffer, int offset, int count)
		{
			if (!readHeader)
			{
				byte[] header = reader.ReadBytes(0x28);
				if (header.Length != 0x28)
					throw new FormatException("Truncated header");
				if (header[0] != 'H' || header[1] != 'U' || header[2] != '0' || header[3] != '1')
					throw new FormatException("Invalid header magic");
				if (BitConverter.ToUInt32(header, 4) != 0x28)
					throw new FormatException("Invalid header size");
				size = BitConverter.ToUInt32(header, 0x20);
				readHeader = true;
			}

			if (outputBuffer == null)
			{
				if (size > 0)
				{
					byte[] tempBuffer = reader.ReadBytes(4);
					if (tempBuffer.Length == 0)
						return 0;
					if (tempBuffer.Length != 4 || tempBuffer[0] != 'S' && tempBuffer[1] != 'C' && tempBuffer[2] != 'B' && tempBuffer[3] != 'H')
						throw new FormatException("Invalid block header");
					uint headerSize = reader.ReadUInt32();
					uint decompressedSize = reader.ReadUInt32();
					uint crc = reader.ReadUInt32();
					uint blockSize = reader.ReadUInt32();
					if (blockSize == decompressedSize && blockSize < 2048)
					{
						// Uncompressed block
						outputBuffer = reader.ReadBytes((int)blockSize);
					}
					else
					{
						// Compressed block
						HuffmanTree tree = new HuffmanTree(reader.ReadBytes(256));
						InputBuffer inputBuffer = new InputBuffer(reader.ReadBytes((int)blockSize - 256));
						outputBuffer = new byte[decompressedSize];
						DecodeBlock(tree, inputBuffer, outputBuffer);
					}
					outputPosition = 0;
					size -= (uint)outputBuffer.Length;
				}
				else
				{
					return 0;
				}
			}

			int bytesToCopy = Math.Min(count, outputBuffer.Length - outputPosition);
			Array.Copy(outputBuffer, outputPosition, buffer, offset, bytesToCopy);
			outputPosition += bytesToCopy;
			if (outputPosition == outputBuffer.Length)
				outputBuffer = null;
			return bytesToCopy;
		}

		public override long Seek(long offset, SeekOrigin origin)
		{
			throw new NotSupportedException();
		}

		public override void SetLength(long value)
		{
			throw new NotSupportedException();
		}

		public override void Write(byte[] buffer, int offset, int count)
		{
			throw new NotSupportedException();
		}
	}
}
